/* Copyright 2011 Google Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
 * USA.
 *
 * Project home page: http://code.google.com/p/usb-serial-for-android/
 */

package com.stu.usb;

import android.hardware.usb.UsbRequest;

import java.io.IOException;
import java.nio.ByteBuffer;

import com.stu.aq.*;

/**
 * Utility class which services a {@link UsbSerialDriver} in its {@link #run()}
 * method.
 *
 * @author mike wakerly (opensource@hoho.com)
 */
public class SerialIOManager implements Runnable {

	private static final String TAG = SerialIOManager.class.getSimpleName();

	private static final int READ_WAIT_MILLIS = 200;
	private static final int BUFSIZ = 4096;

	private final UsbSerialDriver mDriver;

	private ByteBuffer mReadBuffer;

	// Synchronized by 'mWriteBuffer'
	private ByteBuffer mWriteBuffer;

	public enum State {
		STOPPED,
		RUNNING,
		STOPPING
	}

	// Synchronized by 'this'
	private State mState;

	// Synchronized by 'this'
	private Listener mListener;

	public interface Listener {
		/**
		 * Called when new incoming data is available.
		 * Does not run on the UI thread.
		 */
		public void onNewData(final byte[] data);

		/**
		 * Called when {@link SerialIOManager#run()} aborts due to an
		 * error.
		 * Does not run on the UI thread.
		 */
		public void onRunError(Exception e);
	}

	/**
	 * Creates a new instance with no listener.
	 */
	public SerialIOManager(UsbSerialDriver driver) {
		this(driver, null);
	}

	/**
	 * Creates a new instance with the provided listener.
	 */
	public SerialIOManager(UsbSerialDriver driver, Listener listener) {
if(AQ.DEBUG) Log.d( TAG, "ctor" );
		mDriver = driver;
		mListener = listener;
		mReadBuffer = ByteBuffer.allocate(BUFSIZ);
		mWriteBuffer = ByteBuffer.allocate(BUFSIZ);
		mState = State.STOPPED;
		// drain any data in the device buffer
int d = 0;
		while( mDriver.read(mReadBuffer.array(), 500) > 0 ) {
d++;
		}
		mReadBuffer.clear();
if(AQ.DEBUG) Log.d( TAG,  "drain="+d );
	}

	public synchronized void setListener(Listener listener) {
		mListener = listener;
	}

	public synchronized Listener getListener() {
		return mListener;
	}

	public void writeAsync(byte[] data) {
		synchronized (mWriteBuffer) {
			mWriteBuffer.put(data);
		}
	}

	public synchronized void stop() {
		if (getState() == State.RUNNING) {
			if(AQ.DEBUG) Log.d(TAG, "Stop requested");
			mState = State.STOPPING;
		}
	}

	public synchronized State getState() {
		return mState;
	}

	/**
	 * Continuously services the read and write buffers until {@link #stop()} is
	 * called, or until a driver exception is raised.
	 *
	 * NOTE(mikey): Uses inefficient read/write-with-timeout.
	 * TODO(mikey): Read asynchronously with {@link UsbRequest#queue(ByteBuffer, int)}
	 */
	@Override
	public void run() {
		synchronized (this) {
			if (getState() != State.STOPPED) {
				throw new IllegalStateException("Already running.");
			}
			mState = State.RUNNING;
		}

		if(AQ.DEBUG) Log.d(TAG, "Running ..");
		try {
			while (true) {
				if (getState() != State.RUNNING) {
					if(AQ.DEBUG) Log.d(TAG, "Stopping mState=" + getState());
					break;
				}
				step();
			}
		} catch (Exception e) {
			if(AQ.DEBUG) Log.d(TAG, "Run ending due to exception: " + e.getMessage(), e);
			final Listener listener = getListener();
			if (listener != null) {
			  listener.onRunError(e);
			}
		} finally {
			synchronized (this) {
				mState = State.STOPPED;
				mReadBuffer = null;
				mWriteBuffer = null;
				if(AQ.DEBUG) Log.d(TAG, "Stopped.");
			}
		}
	}

	private void step() throws IOException, AQException {
		// Handle incoming data.
		int len = mDriver.read(mReadBuffer.array(), READ_WAIT_MILLIS);
		if (len > 0) {
			if(AQ.DEBUG) Log.d(TAG, "Read data len=" + len);
			final Listener listener = getListener();
			if (listener != null) {
				final byte[] data = new byte[len];
				mReadBuffer.get(data, 0, len);
				listener.onNewData(data);
			}
			mReadBuffer.clear();
		}

		// Handle outgoing data.
		byte[] outBuff = null;
		synchronized (mWriteBuffer) {
			if (mWriteBuffer.position() > 0) {
				len = mWriteBuffer.position();
				outBuff = new byte[len];
				mWriteBuffer.rewind();
				mWriteBuffer.get(outBuff, 0, len);
				mWriteBuffer.clear();
			}
		}
		if (outBuff != null) {
			if(AQ.DEBUG) Log.d(TAG, "Writing data len=" + len);
			mDriver.write(outBuff, READ_WAIT_MILLIS);
		}
	}

}
